﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using System.IO;
using Bravura.Contracts.PluginTypes;
using Bravura.Core.PluginLib;
using Bravura.Native.PluginBase;
using Bravura.Native.PluginBase.Oscillators;
using Bravura.Native.PluginBase.Waveforms;

namespace Bravura.Native.Plugins.Instruments
{
	/// <summary>
	/// Output channels : 2 
	/// Voices : 10
	/// 
	/// Loads a sample and extracts a waveform from the sample. The extraction location and size can change over time.
	/// </summary>
	/// <remarks>
	/// WORK IN PROGRESS
	/// </remarks>
	public class Extractor : Instrument<Extractor.ExtractorVoice>, IInstrument
	{
		#region Properties

		public string SampleFile
		{
			get;
			set;
		}

		public int SampleSkipBytes
		{
			get;
			set;
		}

		public int SampleChannel
		{
			get;
			set;
		}

		public int SampleChannels
		{
			get;
			set;
		}

		public int SampleLength
		{
			get;
			set;
		}

		public double Normalization
		{
			get;
			set;
		}

		public double TransitionTime
		{
			get;
			set;
		}

		#endregion

		private double[] _sampleData = null;

		public Extractor(IAudioPluginHost host, int sampleRate) : base(host, sampleRate, 10)
		{
			this.SampleFile = Path.Combine("Samples", "bones002x.wav");
			this.SampleSkipBytes = 44;
			this.SampleChannel = 0;
			this.SampleChannels = 1;
			this.SampleLength = 150000;
			this.Normalization = 1.0;
		}

		public override void Open()
		{
			_sampleData = Utilities.LoadSample(new FileInfo(SampleFile), SampleChannel, SampleChannels, SampleSkipBytes, SampleLength);
		}

		public override Extractor.ExtractorVoice CreateVoice()
		{
			return new ExtractorVoice(_sampleTime, this);
		}

		public class ExtractorVoice : Voice
		{
			#region Fields

			private Extractor _inst = null;
			private double[] _sampleData = null;

			private double _progTimer = 0.000;
			private double _progPeriod = 0.160;
			private int _progSeqIdx = 0;
			private int _progNum = 0;
			private int _progIdx = 0;
			private int[] _programSeq = new int[] { 0, 0, 0, 1 };
			private int[][] _programs = new int[][]
			{
				new int[] { 3500, 4500, 5500, 6500, 7500, 8500, 9500, 9999, },
				new int[] { 7000, 5000, 7000, 9000, 2000, 9000, 2000, 2500, },
			};

			private Complex[] _data0 = null; // source
			private Complex[] _data1 = null; // target or current

			private DataTableWaveform _dtwf0 = new DataTableWaveform(); // source
			private DataTableWaveform _dtwf1 = new DataTableWaveform(); // target or current

			private Oscillator _osc0 = null; // source
			private Oscillator _osc1 = null; // target or current

			private double _transTime = 0.080;
			private double _transRate = 1.0 / 0.080;
			private double _timeElapsed = 0.0;

			#endregion

			internal ExtractorVoice(double dt, Extractor inst) : base (dt, 1.0 / inst.AttackTime, 1.0 / inst.ReleaseTime, inst.Distortion)
			{
				_inst = inst;
				_sampleData = inst._sampleData;

				Complex[] data = extractWaveformData(_programs[_progNum][_progIdx]);
				_data0 = data;
				_data1 = data;

				generate();

				double phase = Utilities.Random.NextDouble();
				_osc0 = new WaveformOscillator(dt, _dtwf0, phase);
				_osc1 = new WaveformOscillator(dt, _dtwf1, phase);
			}

			public override void NoteOn(double sound, double pitch, double velocity)
			{
				int oldSize = _size;

				base.NoteOn(sound, pitch, velocity);

				if (oldSize != _size)
				{
					generate();
				}

				_osc0.SetFrequency(_freqOrd);
				_osc1.SetFrequency(_freqOrd);
			}

			public override void NoteOff()
			{
				base.NoteOff();
			}

			public override double[] Sample()
			{
				// transitioning?
				if (_timeElapsed < _transTime)
				{
					// update osc amps
					double oscMix = _timeElapsed * _transRate;
					_osc0.ModulateAmplitude(1.0 - oscMix);
					_osc1.ModulateAmplitude(oscMix);

					// update time elapsed
					_timeElapsed += _dt;

					// done?
					if (_timeElapsed >= _transTime)
					{
						_osc0.ModulateAmplitude(0.0);
						_osc1.ModulateAmplitude(1.0);
					}
				}

				// get the sample
				double sc = _osc0.Sample() + _osc1.Sample();

				// process program
				_progTimer += _dt;
				if (_progTimer >= _progPeriod)
				{
					_progTimer -= _progPeriod;

					_progIdx = _progIdx + 1;
					if (_progIdx == _programs[_progNum].Length)
					{
						_progIdx = 0;
						_progSeqIdx = (_progSeqIdx + 1) % _programSeq.Length;
						_progNum = _programSeq[_progSeqIdx];
					}

					int newLoc = _programs[_progNum][_progIdx];

					transition(newLoc);
				}

				// apply distortion ?
				if (_dist != 1.0)
				{
					sc = (sc < 0.0) ? -Math.Pow(-sc, _dist) : Math.Pow(sc, _dist);
				}

				// apply level
				sc *= _velocity * _level * _level;

				// update voice envelope
				UpdateEnvelope();

				// set sample values (panning)
				_sample[0] = _mix0 * sc;
				_sample[1] = _mix1 * sc;

				return _sample;
			}

			private void transition(int index)
			{
				_timeElapsed = 0.0;
				_data0 = _data1;
				_data1 = extractWaveformData(index);
				generate();
			}

			private Complex[] extractWaveformData(int index)
			{
				int z0 = Utilities.FindZero(_sampleData, index - 20, -1);
				int z1 = Utilities.FindZero(_sampleData, index + 20, +1);
				double[] wfData = Utilities.ExtractSlice(_sampleData, z0, z1-z0, _inst.Normalization);
				return Fourier.Analyze(wfData);
			}

			private void generate()
			{
				_dtwf0.SetData(Fourier.Synthesize(_data0, _size, _maxn));
				_dtwf1.SetData(Fourier.Synthesize(_data1, _size, _maxn));
			}
		}
	}
}
